
package dovs.peephole;

import dovs.peephole.node.*;
import dovs.instructions.*;

import java.util.*;

public aspect Evaluate {
	private static Map<PVar, Object> env;
	private static PeepholeDriver driver;
	private static List<Label> bound_labels;

	// Misc stuff
	public abstract TIdentifier POpcode.getIdentifier();
	public abstract TIdentifier PVar.getIdentifier();
	public abstract Token PCond.getToken();
	public abstract POpcode PInst.getOpcode();
	public abstract List<PExp> PInst.getArgs();
	// public abstract List<PPatternDecl> PPatternCollection.getPatterns();
	public abstract TIdentifier PPatternDecl.getName();
	public abstract PVar PPatternDecl.getInstvar();
	public abstract PExp PPatternDecl.getMatch();
	public abstract TIntegerLiteral PPatternDecl.getSize();
	// public abstract List<PInst> PPatternDecl.getReplacement();

	public boolean PVar.equals(Object other) {
		return other instanceof PVar &&
		getIdentifier().getText().equals(((PVar)other).getIdentifier().getText());
	}

	public int PVar.hashCode() {
		return getIdentifier().getText().hashCode();
	}


	// INIT

	public static void init(PPatternDecl pattern, InstructionList ilp_prev,
			PeepholeDriver _driver_) {
		env = new HashMap<PVar, Object>();
		env.put(pattern.getInstvar(), ilp_prev);
		Evaluate.driver = _driver_;
		bound_labels = new LinkedList<Label>();
	}

	public static Object getEnv(PVar var) {
		return env.get(var);
	}


	// EXPRESSIONS

	public abstract Object PExp.eval();

	public Object AVarExp.eval() {
		Object v = env.get(getVar());
		if (v == null) {
			throw new PeepholeException("Variable "+getVar().getIdentifier().getText()+" is null");
		}
		return v;
	}

	public Object AUnopExp.eval() {
		return getUnop().op(getExp().eval());
	}

	public Object ABinopExp.eval() {
		return getBinop().op(getLeft().eval(), getRight().eval());
	}

	public Object ABuiltinExp.eval() {
		return getBuiltin().op(getExp().eval());
	}

	public Object AIntConstExp.eval() {
		return value;
	}

	public Object ACondConstExp.eval() {
		return value;
	}

	public Object AConjunctionExp.eval() {
		return (Boolean)getLeft().eval() && (Boolean)getRight().eval();
	}

	public Object ADisjunctionExp.eval() {
		if ((Boolean)getLeft().eval()) {
			// flushBindings(this.bound_vars);
			return true;
		}
		// flushBindings(Collections.<PVar>emptySet());
		if ((Boolean)getRight().eval()) {
			// flushBindings(this.bound_vars);
			return true;
		}
		// flushBindings(Collections.<PVar>emptySet());
		return false;
	}

	public Object AMatchExp.eval() {
		InstructionList ilp_prev = (InstructionList)getLeft().eval();
		for (PInstPat inst : getInstructions()) {
			if ((ilp_prev = inst.match(ilp_prev)) == null) {
				// flushBindings(Collections.<PVar>emptySet());
				return false;
			}
		}
		return true;
	}


	// INSTRUCTION MATCHING

	public abstract InstructionList PInstPat.match(InstructionList ilp_prev);

	public InstructionList AInstructionInstPat.match(InstructionList ilp_prev) {
		while (ilp_prev.next != null && ilp_prev.next.inst instanceof Ilabel && driver.label_degree.get(((Ilabel)ilp_prev.next.inst).getLabel()) == 0) {
			ilp_prev = ilp_prev.next;
		}
		if (ilp_prev.next != null) {
			Instruction i = ilp_prev.next.inst;
			if (i.getID() == getOpcode().inst_id) {
				int a = 0;
				for (AVar v : getParams()) {
					env.put(v, i.getArg(a++));
				}
				return ilp_prev.next;
			}
		}
		return null;
	}

	public InstructionList ALabelBinderInstPat.match(InstructionList ilp_prev) {
		if (!(ilp_prev.inst instanceof Ilabel) && ilp_prev.next != null) {
			if (ilp_prev.next.inst instanceof Ilabel) {
				env.put(getLabel(), ((Ilabel)ilp_prev.next.inst).getLabel());
				return ilp_prev.next;
			} else {
				Label l = Label.make("labelbinder");
				InstructionList new_label = driver.add(ilp_prev, new Ilabel(l));
				env.put(getLabel(), l);
				bound_labels.add(l);
				return new_label;
			}
		}
		return null;
	}

	public InstructionList AWildcardInstPat.match(InstructionList ilp_prev) {
		while (ilp_prev.next != null && ilp_prev.next.inst instanceof Ilabel && driver.label_degree.get(((Ilabel)ilp_prev.next.inst).getLabel()) == 0) {
			ilp_prev = ilp_prev.next;
		}
		if (ilp_prev.next != null && !(ilp_prev.next.inst instanceof Ilabel)) {
			return ilp_prev.next;
		}
		return null;
	}
	/*
	 * private void flushBindings(Set<PVar> exceptions) { for (Iterator<Label>
	 * li = bound_labels.iterator() ; li.hasNext() ;) { Label l = li.next(); if
	 * (!exceptions.contains(l)) { driver.removeLabel(l); li.remove(); } } }
	 */

	// UNARY OPERATIONS

	public abstract Object PUnop.op(Object val);

	public Object ANegateUnop.op(Object val) {
		return -(Integer)val;
	}

	public Object AComplementUnop.op(Object val) {
		// flushBindings(Collections.<PVar>emptySet());
		return !(Boolean)val;
	}


	// BINARY OPERATIONS

	public abstract Object PBinop.op(Object left, Object right);

	public Object APlusBinop.op(Object left, Object right) {
		return (Integer)left + (Integer)right;
	}

	public Object AMinusBinop.op(Object left, Object right) {
		return (Integer)left - (Integer)right;
	}

	public Object ATimesBinop.op(Object left, Object right) {
		return (Integer)left * (Integer)right;
	}

	public Object ADivideBinop.op(Object left, Object right) {
		return (Integer)left / (Integer)right;
	}

	public Object AModuloBinop.op(Object left, Object right) {
		return (Integer)left % (Integer)right;
	}

	public Object AEqBinop.op(Object left, Object right) {
		return left.equals(right);
	}

	public Object ANeBinop.op(Object left, Object right) {
		return !left.equals(right);
	}

	public Object ALtBinop.op(Object left, Object right) {
		return (Integer)left < (Integer)right;
	}

	public Object ALeBinop.op(Object left, Object right) {
		return (Integer)left <= (Integer)right;
	}

	public Object AGtBinop.op(Object left, Object right) {
		return (Integer)left > (Integer)right;
	}

	public Object AGeBinop.op(Object left, Object right) {
		return (Integer)left >= (Integer)right;
	}

	public Object AAndBinop.op(Object left, Object right) {
		return (Integer)left & (Integer)right;
	}

	public Object AOrBinop.op(Object left, Object right) {
		return (Integer)left | (Integer)right;
	}

	public Object AXorBinop.op(Object left, Object right) {
		return (Integer)left ^ (Integer)right;
	}

	// BUILTIN FUNCTIONS

	public abstract Object PBuiltin.op(Object val);

	public Object ACommuteBuiltin.op(Object val) {
		return ((Condition)val).commute();
	}

	public Object ANegateBuiltin.op(Object val) {
		return ((Condition)val).negate();
	}

	public Object ADegreeBuiltin.op(Object val) {
		return driver.label_degree.get(val);
	}

	public Object AFormalsBuiltin.op(Object val) {
		return ((MethodSignature)val).numberOfArguments();
	}

	public Object AReturnsBuiltin.op(Object val) {
		return ((MethodSignature)val).numberOfReturns();
	}

	public Object ATargetBuiltin.op(Object val) {
		InstructionList target = driver.label_target.get(val);
		if (target == null) {
			throw new PeepholeException("Target of label "+((Label)val).getName()+" is null");
		}
		return target;
	}

}
